<?php

namespace Louis\Router;
use Louis\Container;
use Louis\Http\Request;
use Louis\Http\Response;

class Dispatcher {
    //use RouteStrategyTrait;

    const NOT_FOUND = 0;
    const FOUND = 1;
    const METHOD_NOT_ALLOWED = 2;
    /** @var mixed[][] */
    protected $staticRouteMap = [];

    /** @var mixed[] */
    protected $variableRouteData = [];

    /**
     * @var \Container
     */
    protected $container;

    public function __construct(Container $container, array $data) {
        $this->container = $container;

        list($this->staticRouteMap, $this->variableRouteData) = $data;
    }
    /**
     *
     * @return mixed[]
     */
    protected function dispatchVariableRoute($routeData, $uri) {
        foreach ($routeData as $data) {
            if (!preg_match($data['regex'], $uri, $matches)) {
                continue;
            }

            list($handler, $varNames) = $data['routeMap'][count($matches)];

            $vars = [];
            $i = 0;
            foreach ($varNames as $varName) {
                $vars[$varName] = $matches[++$i];
            }
            return [self::FOUND, $handler, $vars];
        }

        return [self::NOT_FOUND];
    }

    public function dispatch($httpMethod, $uri) {
        if (isset($this->staticRouteMap[$httpMethod][$uri])) {
            $handler = $this->staticRouteMap[$httpMethod][$uri];
            return [self::FOUND, $handler, []];
        }

        $varRouteData = $this->variableRouteData;
        if (isset($varRouteData[$httpMethod])) {
            $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
            if ($result[0] === self::FOUND) {
                return $result;
            }
        }

        // For HEAD requests, attempt fallback to GET
        if ($httpMethod === 'HEAD') {
            if (isset($this->staticRouteMap['GET'][$uri])) {
                $handler = $this->staticRouteMap['GET'][$uri];
                return [self::FOUND, $handler, []];
            }
            if (isset($varRouteData['GET'])) {
                $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
                if ($result[0] === self::FOUND) {
                    return $result;
                }
            }
        }

        // If nothing else matches, try fallback routes
        if (isset($this->staticRouteMap['*'][$uri])) {
            $handler = $this->staticRouteMap['*'][$uri];
            return [self::FOUND, $handler, []];
        }
        if (isset($varRouteData['*'])) {
            $result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
            if ($result[0] === self::FOUND) {
                return $result;
            }
        }

        // Find allowed methods for this URI by matching against all other HTTP methods as well
        $allowedMethods = [];

        foreach ($this->staticRouteMap as $method => $uriMap) {
            if ($method !== $httpMethod && isset($uriMap[$uri])) {
                $allowedMethods[] = $method;
            }
        }

        foreach ($varRouteData as $method => $routeData) {
            if ($method === $httpMethod) {
                continue;
            }

            $result = $this->dispatchVariableRoute($routeData, $uri);
            if ($result[0] === self::FOUND) {
                $allowedMethods[] = $method;
            }
        }

        // If there are no allowed methods the route simply does not exist
        if ($allowedMethods) {
            return [self::METHOD_NOT_ALLOWED, $allowedMethods];
        } else {
            return [self::NOT_FOUND];
        }
    }

    public function respond(){
        $request = $this->container->get("request");
        $httpMethod = $request->getMethod();
        $uri = $request->getPathInfo();
        $match = $this->dispatch($httpMethod, $uri);

        switch ($match[0]) {
            case static::NOT_FOUND:
                $response = $this->handleNotFound();
                break;
            case static::METHOD_NOT_ALLOWED:
                $allowed  = (array) $match[1];
                $response = $this->handleNotAllowed($allowed);
                break;
            case static::FOUND:
            default:
                $handler  = $match[1];
                $vars     = (array) $match[2];
                $response = $this->handleFound($handler, $vars);
                break;
        }

        return $response;
    }

    /**
     * Handle dispatching of a found route
     *
     * @param  string|\Closure                             $handler
     * @param  array                                       $vars
     * @return Response
     * @throws RuntimeException
     */
    protected function handleFound($handler, array $vars)
    {
        $callable = explode("::", $handler);
        $response = $this->container->get($handler)->{$callable[1]}($vars);
        if ($response instanceof Response) {
            return $response;
        }

        throw new \RuntimeException(
            'When using the Request -> Response your controller must return an instance of Response'
        );
    }




    /**
     * Attempt to build a response
     *
     * @param  mixed $response
     * @return Response
     */
    protected function determineResponse($response)
    {
        if ($response instanceof ResponseInterface) {
            return $response;
        }

        try {
            $response = new Response($response);
        } catch (\Exception $e) {
            throw new \RuntimeException('Unable to build Response from controller return value', 0, $e);
        }

        return $response;
    }

    /**
     * Handle a not found route
     *
     * @return \Orno\Http\ResponseInterface
     */
    protected function handleNotFound()
    {
        $exception = new HttpException\NotFoundException;

        if ($this->getStrategy() === RouteStrategyInterface::RESTFUL_STRATEGY) {
            return $exception->getJsonResponse();
        }

        throw $exception;
    }

    /**
     * Handles a not allowed route
     *
     * @param  array $allowed
     * @return \Orno\Http\ResponseInterface
     */
    protected function handleNotAllowed(array $allowed)
    {
        $exception = new HttpException\MethodNotAllowedException($allowed);

        if ($this->getStrategy() === RouteStrategyInterface::RESTFUL_STRATEGY) {
            return $exception->getJsonResponse();
        }

        throw $exception;
    }

}